﻿using AppUpdateInstaller.Model;
using AppUpdateInstaller.Model.InstallationStages;
using ConsoleHoster.Common.Utilities;
using ConsoleHoster.Common.Utilities.IO;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;
using System;
using TestUtilities;

namespace AppUpdateInstaller.UnitTests
{
	[TestClass]
	public class InstallationStageFactoryTests
	{
		private Mock<IProcessManager> mockProcessManager;
		private Mock<IFileSystemContentProvider> mockFSContentProvider;

		private ActionMock mockShutdownAction;

		[TestInitialize]
		public void InitializeTest()
		{
			this.mockProcessManager = new Mock<IProcessManager>();
			this.mockFSContentProvider = new Mock<IFileSystemContentProvider>();

			this.mockShutdownAction = new ActionMock();
		}

		#region Failure Scenario Tests
		[TestMethod]
		[ExpectedException(typeof(ArgumentNullException))]
		public void Constructor_ThrowsIfArgsAreNull()
		{
			new InstallationStageFactory(null, this.ProcessManager, this.FSContentProvider, this.ShutdownAction);
		}

		[TestMethod]
		[ExpectedException(typeof(ArgumentNullException))]
		public void Constructor_ThrowsIfArgsIsEmptyArray()
		{
			new InstallationStageFactory(new string[0] { }, this.ProcessManager, this.FSContentProvider, this.ShutdownAction);
		}

		[TestMethod]
		[ExpectedException(typeof(ArgumentNullException))]
		public void Constructor_ThrowsIfProcessManagerIsNull()
		{
			new InstallationStageFactory(new[] { "someValue" }, null, this.FSContentProvider, this.ShutdownAction);
		}

		[TestMethod]
		[ExpectedException(typeof(ArgumentNullException))]
		public void Constructor_ThrowsIfFSContentProviderIsNull()
		{
			new InstallationStageFactory(new[] { "someValue" }, this.ProcessManager, null, this.ShutdownAction);
		}

		[TestMethod]
		[ExpectedException(typeof(ArgumentException))]
		public void Create_ThrowsForIntAsInstallerStageArgument()
		{
			this.CreateWithInstallerStageArgumentParameter("6");
		}

		[TestMethod]
		[ExpectedException(typeof(ArgumentException))]
		public void Create_ThrowsForUnknownInstallerStageArgument()
		{
			this.CreateWithInstallerStageArgumentParameter("UnknownStage");
		}
		#endregion

		#region Success Scenario Tests
		[TestMethod]
		public void Create_ReturnsCompositeStage()
		{
			string tmpModeParam = InstallerMode.ExtractArchive.ToString();
			string tmpArchivePath = "archive Path";
			string tmpBinariesFolder = "binaries Path";
			string[] tmpArgs = new[] { tmpModeParam, tmpArchivePath, tmpBinariesFolder };

			this.mockFSContentProvider
				.Setup(fs => fs.DirectoryExists(tmpBinariesFolder))
				.Returns(true);
			this.mockFSContentProvider
				.Setup(fs => fs.FileExists(tmpArchivePath))
				.Returns(true);
			this.mockFSContentProvider
				.Setup(fcp => fcp.GetFileDirectory(tmpArchivePath))
				.Returns(@"C:\some path");
			this.mockFSContentProvider
				.Setup(fcp => fcp.GetRandomFolderName())
				.Returns("asdf");

			InstallationStageFactory tmpFactory = new InstallationStageFactory(tmpArgs, this.ProcessManager, this.FSContentProvider, this.ShutdownAction);
			IInstallationStage tmpResult = tmpFactory.Create();

			Assert.IsTrue(tmpResult is CompositeStage, "The created installer stage is not of the expected type");

			this.mockFSContentProvider.Verify(fs => fs.DirectoryExists(tmpBinariesFolder), Times.Exactly(2));
		}

		[TestMethod]
		public void Create_ReturnsUpdateBinariesStage()
		{
			string tmpModeParam = InstallerMode.UpdateBinaries.ToString();
			string tmpUpdateFolderPath = "update Folder Path";
			string tmpBinariesFolder = "binaries Path";
			string[] tmpArgs = new[] { tmpModeParam, tmpUpdateFolderPath, tmpBinariesFolder };

			this.mockFSContentProvider
				.Setup(fs => fs.DirectoryExists(tmpBinariesFolder))
				.Returns(true);
			this.mockFSContentProvider
				.Setup(fs => fs.DirectoryExists(tmpUpdateFolderPath))
				.Returns(true);

			InstallationStageFactory tmpFactory = new InstallationStageFactory(tmpArgs, this.ProcessManager, this.FSContentProvider, this.ShutdownAction);
			IInstallationStage tmpResult = tmpFactory.Create();

			Assert.IsTrue(tmpResult is UpdateBinariesStage, "The created installer stage is not of the expected type");

			this.mockFSContentProvider.Verify(fs => fs.DirectoryExists(tmpBinariesFolder), Times.Exactly(1));
			this.mockFSContentProvider.Verify(fs => fs.DirectoryExists(tmpUpdateFolderPath), Times.Exactly(1));
		}

		[TestMethod]
		public void Create_ReturnsRemoveUpdateFolderStage()
		{
			string tmpModeParam = InstallerMode.CleanupUpdateBinaries.ToString();
			string tmpCurrentDirectory = "Current Dir";
			string tmpUpdateFolderPath = "update Folder Path";
			string tmpBinariesFolder = "binaries Path";
			string[] tmpArgs = new[] { tmpModeParam, tmpUpdateFolderPath, tmpBinariesFolder };

			this.mockFSContentProvider
				.Setup(fs => fs.DirectoryExists(tmpBinariesFolder))
				.Returns(true);
			this.mockFSContentProvider
				.Setup(fs => fs.DirectoryExists(tmpUpdateFolderPath))
				.Returns(true);
			this.mockFSContentProvider
				.Setup(fs => fs.GetCurrentDirectory())
				.Returns(tmpCurrentDirectory);

			InstallationStageFactory tmpFactory = new InstallationStageFactory(tmpArgs, this.ProcessManager, this.FSContentProvider, this.ShutdownAction);
			IInstallationStage tmpResult = tmpFactory.Create();

			Assert.IsTrue(tmpResult is RemoveUpdateFolderStage, "The created installer stage is not of the expected type");

			this.mockFSContentProvider.Verify(fs => fs.DirectoryExists(tmpBinariesFolder), Times.Exactly(1));
			this.mockFSContentProvider.Verify(fs => fs.DirectoryExists(tmpUpdateFolderPath), Times.Exactly(1));
		}
		#endregion

		#region Helper methods
		private void CreateWithInstallerStageArgumentParameter(string tmpModeParam)
		{
			string[] tmpArgs = new[] { tmpModeParam, "some path 1", "some path 2" };
			new InstallationStageFactory(tmpArgs, this.ProcessManager, this.FSContentProvider, this.ShutdownAction);
		}
		#endregion

		#region Properties
		private IProcessManager ProcessManager
		{
			get
			{
				return this.mockProcessManager.Object;
			}
		}

		private IFileSystemContentProvider FSContentProvider
		{
			get
			{
				return this.mockFSContentProvider.Object;
			}
		}

		private int ActionCallCount
		{
			get
			{
				return this.mockShutdownAction.CalledTiems;
			}
		}

		private Action ShutdownAction
		{
			get
			{
				return this.mockShutdownAction.Action;
			}
		}
		#endregion
	}
}
